package Task24;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Map;
import Task24.Controller.DirectoryIsRootException;
import Task24.Controller.FileAlreadyExistException;
import Task24.Controller.MyFileNotFoundException;

public class Model {
	private static String workDir = "C:\\";
	private static String command;
	private static String arg1;
	private static String arg2;
	private static int numArg;

	private boolean mapExist = false; // map not create yet.
	private static Map<String, CommandInterface> commandMap;

	public void setArg1(String value) {
		arg1 = value;
	}
	public String getArg1() {
		return arg1;
	}

	public void setArg2(String value) {
		arg2 = value;
	}
	public String getArg2() {
		return arg2;
	}

	public void setNumArg(int value) {
		numArg = value;
	}
	public int getNumArg() {
		return numArg;
	}

	public void setCommand(String value) {
		command = value;
	}
	public String getCommand() {
		return command;
	}

	public void setWorkDir(String value) {
		workDir = value;
	}
	public String getWorkDir() {
		return workDir;
	}


	private void createMap() {
		commandMap = new HashMap<String, CommandInterface>();
		CommandInterface instance;
		for (Class<?> cl : new Model().getClass().getDeclaredClasses()) {
			try {
				instance = (CommandInterface) cl.getConstructor(Model.class)
						.newInstance(new Model());
				commandMap.put(
						(String) instance.getClass().getDeclaredField("id")
								.get(null), instance);

			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (NoSuchFieldException e) {
				e.printStackTrace();
			}

		}

	}

	public CommandInterface getObjectCommand(String id)
			throws NullPointerException {
		if (!mapExist) {
			createMap();
			mapExist = true;
		}
		CommandInterface myInst = commandMap.get(id);
		return myInst;
	}

	public class Dir extends CommandInterface {
		public static final String id = "dir";
		public boolean happendError = false;

		public ArrayList<String> getResult() {
			ArrayList<String> list = new ArrayList<String>();

			File f = new File(arg1);
			String[] dirList = f.list();
			String[] printString = new String[dirList.length];

			int k = 0;
			String[] fileList = new String[dirList.length];

			for (int i = 0; i < dirList.length; i++) {
				File f1 = new File(arg1 + File.separator + dirList[i]);
				if (!f1.isFile()) {
					list.add(dirList[i]);
				} else {
					fileList[k] = dirList[i];
					k++;
				}
			}
			for (int i = 0; i < k; i++) {
				list.add(fileList[i]);
			}
			return list;
		}

		public String getError() {
			boolean bResult = true;
			String result = "";
			if (workDir.equals(null)) {
				result += "Null in direcory\n";
				bResult = false;
			}
			if (numArg > 1) {
				result += "Bad format of command\n";
				bResult = false;
			}
			if (numArg == 1)
				arg1 = getAbsPath(workDir, arg1);
			if (numArg == 0)
				arg1 = workDir;
			if (!pathCheck(arg1)) {
				result += "Directory not found\n";
				bResult = false;
			}
			happendError = !bResult;
			return result;
		}
	}

	public class Tree extends CommandInterface {
		public static final String id = "tree";
		public boolean happendError = false;

		private ArrayList<String> list;

		private void printNameDir(String directory, String dirList, int y) {
			File f1 = new File(directory + File.separator + dirList);
			String printString = "";
			if (!f1.isFile()) {
				for (int j = 0; j < y; j++)
					printString += "  ";
				printString += "+ " + dirList;
				list.add(printString);
				printString = "";
			} else {
				for (int j = 0; j < y; j++)
					printString += "  ";
				printString += "  " + dirList;
				list.add(printString);
				printString = "";
			}
		}

		private void insertedTree(String directory, int y) {
			// y - level inserted. Start from 0. Increase.
			File f = new File(directory);
			String[] dirList = f.list();
			for (int i = 0; i < dirList.length; i++) {
				printNameDir(directory, dirList[i], y);
				// if dirList[i] is directory then call printTree for dirList[i]
				String absDir = directory + File.separator + dirList[i];
				File f1 = new File(absDir);
				if (!f1.isFile())
					insertedTree(absDir, y + 1);
			}
		}

		public ArrayList<String> getResult() {
			list = new ArrayList<String>();
			insertedTree(arg1, 0);
			return list;
		}

		public String getError() {
			boolean bResult = true;
			String result = "";
			if (workDir.equals(null)) {
				result += "Null in direcory\n";
				bResult = false;
			}
			if (numArg > 1) {
				result += "Bad format of command\n";
				bResult = false;
			}
			if (numArg == 1)
				arg1 = getAbsPath(workDir, arg1);
			if (numArg == 0)
				arg1 = workDir;
			if (!pathCheck(workDir)) {
				result += "Directory not found\n";
				bResult = false;
			}
			happendError = !bResult;
			return result;
		}
	}

	public class Find extends CommandInterface {
		public static final String id = "find";
		public boolean happendError = false;

		private ArrayList<String> list;

		private void searchInTreeRecursion(String directory, String mask) {
			// Method runs searchInDir only in folder - 'dir' and run itself in
			// all include folders
			File f = new File(directory);
			searchInDir(directory, mask);
			String[] dirList = f.list();
			for (int i = 0; i < dirList.length; i++) {
				String absDir = directory + File.separator + dirList[i];
				File f1 = new File(absDir);
				if (!f1.isFile())
					searchInTreeRecursion(absDir, mask);
			}

		}

		public void searchInDir(String directory, String mask) {
			File f = new File(directory);
			String[] dirList = f.list();
			String[] arrayMask;

			if (mask.contains("*"))
				mask = mask.replace("*", "`");
			if (mask.contains("`")) {
				arrayMask = mask.split("`");

				for (int i = 0; i < dirList.length; i++) {
					String sDirList = dirList[i];
					int k = 0;
					while (sDirList.length() > 0 && k < arrayMask.length) {
						if (sDirList.startsWith(arrayMask[k])) {
							for (int r = 0; r < arrayMask[k].length(); r++)
								sDirList = cutDir(sDirList);
							k++;
						} else {
							sDirList = cutDir(sDirList);
						}
					}
					if (k >= arrayMask.length)
						printName(directory + File.separator + dirList[i]);
				}

			} else {
				for (int i = 0; i < dirList.length; i++) {
					if (dirList[i].equals(mask))
						printName(directory + File.separator + dirList[i]);
				}
			}

		}

		private String cutDir(String directory) {
			int ld = directory.length();
			char[] newDir = new char[ld - 1];
			directory.getChars(1, ld, newDir, 0);
			String sNewDir = newDir.toString();
			sNewDir = sNewDir.copyValueOf(newDir);
			return sNewDir;
		}

		private void printName(String dirName) {
			File f1 = new File(dirName);
			Date date = new Date(f1.lastModified());
			list.add(f1.getName() + " - " + date + " - " + f1.getPath());

		}

		public ArrayList<String> getResult() {

			list = new ArrayList<String>();
			searchInTreeRecursion(arg1, arg2);

			return list;

		}

		public String getError() {
			boolean bResult = true;
			String result = "";
			if (workDir.equals(null)) {
				result += "Null in direcory\n";
				bResult = false;
			}
			if (!(numArg == 2 || numArg == 1)) {
				result += "Bad format of command\n";
				bResult = false;
			}
			if (numArg == 2)
				arg1 = getAbsPath(workDir, arg1);
			if (numArg == 1) {
				arg2 = arg1;
				arg1 = workDir;
			}
			if (!pathCheck(arg1)) {
				result += "Directory not found\n";
				bResult = false;
			}
			happendError = !bResult;
			return result;
		}
	}

	public class Copy extends CommandInterface {
		public static final String id = "copy";
		public boolean happendError = false;

		public ArrayList<String> getResult() throws FileNotFoundException,
				FileAlreadyExistException, IOException {
			boolean result = false;
			ArrayList<String> sResult = new ArrayList<String>();
			String sFileFrom = arg1;
			String sFileTo = arg2;

			File fileFrom = new File(sFileFrom);
			File fileTo = new File(sFileTo);

			if (fileTo.exists())
				throw new FileAlreadyExistException();

			if (fileFrom.isFile())
				result = copyFile(fileFrom, fileTo);
			else
				result = copyDir(fileFrom, fileTo);

			happendError = !result;
			if (result)
				sResult.add("Operation complete.");
			else
				sResult.add("Error. Not coppy.");
			return sResult;
		}

		public String getError() {
			boolean bResult = true;
			String result = "";
			if (workDir.equals(null)) {
				result += "Null in direcory\n";
				bResult = false;
			}
			if (numArg != 2) {
				result += "Bad format of command\n";
				bResult = false;
			} else {
				arg1 = getAbsPath(workDir, arg1);
				// arg2 = getAbsPath(workDir, arg2);
			}
			if (!pathCheck(arg1)) {
				result += "File not found\n";
				bResult = false;
			}
			if (!pathCheck(workDir)) {
				result += "Directory not found\n";
				bResult = false;
			}
			happendError = !bResult;
			return result;
		}

		private boolean copyFile(File fileFrom, File fileTo)
				throws FileNotFoundException, FileAlreadyExistException,
				IOException {
			boolean result = false;
			InputStream in = new FileInputStream(fileFrom);
			OutputStream out = new FileOutputStream(fileTo);
			byte[] buf = new byte[1024];
			int len;
			while ((len = in.read(buf)) > 0) {
				out.write(buf, 0, len);
			}
			in.close();
			out.close();
			result = true;
			happendError = !result;
			return result;
		}

		private boolean copyDir(File fileFrom, File fileTo)
				throws FileNotFoundException, FileAlreadyExistException,
				IOException {
			// Copy all tree directory
			boolean result = fileTo.mkdir();

			if (fileFrom.list().length > 0) {
				String[] copyList = fileFrom.list();
				boolean flag = true;
				for (int i = 0; i < copyList.length; i++) {
					String sFileFromList = fileFrom.getAbsolutePath()
							+ File.separator + copyList[i];
					File fileFromList = new File(sFileFromList);
					if (fileFromList.isDirectory()) {
						File newFile = new File(fileTo.getAbsoluteFile()
								+ File.separator + fileFromList.getName());
						if (!copyDir(fileFromList, newFile))
							flag = false;
					} else {
						File newFile = new File(fileTo.getAbsoluteFile()
								+ File.separator + fileFromList.getName());
						if (!copyFile(fileFromList, newFile)) {
							flag = false;
							break;
						}
					}
				}
				// flag = true if all files were copy from directory
				result = flag;
			}

			return result;
		}
	}

	public class Move extends CommandInterface {
		public static final String id = "move";
		public boolean happendError = false;

		public ArrayList<String> getResult() throws FileNotFoundException,
				FileAlreadyExistException, IOException {
			ArrayList<String> sResult = new ArrayList<String>();
			boolean bResult = false;
			String fileFrom = arg1;
			String fileTo = arg2;
			Copy myCopy = new Copy();

			String arg2Copy = arg2;
			sResult.add(myCopy.getError()); // Change arg2 path, because it is
											// not
											// exist !!! We should save it!
			arg2 = arg2Copy;
			if (!((CommandInterface) myCopy).happendError)
				sResult = myCopy.getResult();

			if (!((CommandInterface) myCopy).happendError) {
				Del myDel = new Del();
				if (myDel.delFile(fileFrom)) {
					bResult = true;
					sResult.add(" Old file deleted.");
				}
			}
			happendError = !bResult;
			return sResult;
		}

		public String getError() {
			boolean bResult = true;
			String result = "";
			if (workDir.equals(null)) {
				result += "Null in direcory\n";
				bResult = false;
			}
			if (numArg != 2) {
				result += "Bad format of command\n";
				bResult = false;
			} else {
				arg1 = getAbsPath(workDir, arg1);
				// arg2 = getAbsPath(workDir, arg2);
			}
			if (!pathCheck(arg1)) {
				result += "File not found\n";
				bResult = false;
			}
			if (!pathCheck(workDir)) {
				result += "Directory not found\n";
				bResult = false;
			}
			happendError = !bResult;
			return result;
		}
	}

	public class Del extends CommandInterface {
		public static final String id = "del";
		public boolean happendError = false;

		public boolean delFile(String directory) {
			boolean bResult = false;
			File delDir = new File(directory);
			if (delDir.delete())
				bResult = true;
			else
				delDir.deleteOnExit();
			return bResult;

		}

		public boolean delAll(String directory) {
			// Deleted all tree directory
			boolean result = false;
			File delDir = new File(directory);

			if (delDir.list().length > 0) {
				String[] delList = delDir.list();
				boolean flag = true;
				for (int i = 0; i < delList.length; i++) {
					String sFileFromList = directory + File.separator
							+ delList[i];
					File fileFromList = new File(sFileFromList);
					if (fileFromList.isDirectory()) {
						if (!delAll(sFileFromList))
							flag = false;
					} else {
						if (!delFile(sFileFromList)) {
							flag = false;
							break;
						}
					}
				}
				// flag = true if all files were deleted from directory
				if (flag = true)
					result = delFile(directory);
			} else
				result = delFile(directory);

			return result;
		}

		public ArrayList<String> getResult() {
			// Deleted all tree directory
			boolean result = false;
			File delDir = new File(arg1);
			if (delDir.isDirectory())
				result = delAll(arg1);
			else
				result = delFile(arg1);
			happendError = !result;
			ArrayList<String> aResult = new ArrayList<String>();
			aResult.add("Operation complete.");
			return aResult;
		}

		public String getError() {
			boolean bResult = true;
			String result = "";
			if (workDir.equals(null)) {
				result += "Null in direcory\n";
				bResult = false;
			}
			if (numArg != 1) {
				result += "Bad format of command\n";
				bResult = false;
			}
			if (numArg == 1)
				arg1 = getAbsPath(workDir, arg1);
			if (!pathCheck(arg1)) {
				result += "Directory not found\n";
				bResult = false;
			}
			happendError = !bResult;
			return result;
		}
	}

	public class Cd extends CommandInterface {
		public static final String id = "cd";
		public boolean happendError = false;

		private boolean dirIsRoot(File fDir) {
			// Check if work directory is root.
			boolean bDirIsRoot = false;
			File[] arrayRoots = fDir.listRoots();
			for (int i = 0; i < arrayRoots.length; i++) {
				if (arrayRoots[i].equals(fDir))
					bDirIsRoot = true;
			}

			return bDirIsRoot;
		}

		private class GoodPath {
			private String directory;
			private int numberPaths;

			// tets: f:\all\..\all\test\..\..
			public GoodPath(String dir) {
				directory = dir;
			}

			public String getResult() {
				String[] arrayDir = getArray();
				String root = arrayDir[numberPaths];
				String result = "";
				boolean flag = true; // flag==true means that we cut one path
										// with ".." //first value==true for
										// start while

				while (flag) {
					flag = false;
					for (int i = numberPaths; i >= 0; i--) {
						if (arrayDir[i].equals("..")) {
							cutPartArray(arrayDir, i);
							flag = true;
							break;
						}
					}
				}
				if (numberPaths < 0)
					result = root;

				for (int i = numberPaths; i >= 0; i--) {
					result += arrayDir[i] + File.separator;
				}
				return result;
			}

			public String[] getArray() {
				File fDir = new File(directory);
				String[] arrayDir = new String[directory.length()];
				int i = 0;

				while (!dirIsRoot(fDir)) {
					arrayDir[i] = fDir.getName();
					i++;
					fDir = fDir.getParentFile();
					if (fDir == null)
						break;
				}

				if (fDir != null) {
					arrayDir[i] = fDir.toString();
					numberPaths = i;
				} else
					numberPaths = i - 1;

				return arrayDir;
			}

			private String[] cutPartArray(String[] arrayDir, int numUp)
			// cut path with number numUp and next path
			// numUp means path ".."
			{
				for (int i = numUp; i + 2 <= numberPaths; i++) {
					arrayDir[i] = arrayDir[i + 2];
				}
				numberPaths = numberPaths - 2;
				return arrayDir;
			}
		}

		public ArrayList<String> getResult() throws DirectoryIsRootException,
				MyFileNotFoundException {
			String newDir = arg1;
			String directory = workDir;

			GoodPath myDir = new GoodPath(newDir);
			String[] arrayPath = myDir.getArray();

			for (int i = myDir.numberPaths; i >= 0; i--) {
				directory = changeDir(directory, arrayPath[i]);
			}

			ArrayList<String> aResult = new ArrayList<String>();

			File fDir = new File(directory);
			if (fDir.isDirectory()) {
				workDir = directory;
				aResult.add(directory);
			} else {
				aResult.add("It is file.");
			}

			return aResult;
		}

		public String changeDir(String directory, String newDir)
				throws DirectoryIsRootException, MyFileNotFoundException {
			File fDir = new File(directory);

			if (newDir.equals("..")) {
				if (!dirIsRoot(fDir))
					directory = fDir.getParent();
				else {
					happendError = true;
					throw new DirectoryIsRootException();
				}
			} else {
				File fArg = new File(newDir);
				if (!fArg.exists())
					newDir = directory + File.separator + newDir;
				File fNewDir = new File(newDir);
				if (!fNewDir.exists())
					throw new MyFileNotFoundException();
				else
					directory = newDir;
			}

			return directory;
		}

		public String getError() {
			boolean bResult = true;
			String result = "";
			if (workDir.equals(null)) {
				result += "Null in direcory\n";
				bResult = false;
			}
			if (numArg != 1) {
				result += "Bad format of command\n";
				bResult = false;
			}
			if (!pathCheck(workDir)) {
				result += "Directory not found\n";
				bResult = false;
			}
			happendError = !bResult;
			return result;
		}
	}

	public class Mkdir extends CommandInterface {
		// Not work for ABSOLUT path!!!
		public static final String id = "mkdir";
		public boolean happendError = false;

		public ArrayList<String> getResult() {
			String sNewDir = arg1;
			File newDir = new File(sNewDir);
			happendError = newDir.mkdirs();
			ArrayList<String> aResult = new ArrayList<String>();
			aResult.add("Operation compete.");
			return aResult;
		}

		public String getError() {
			boolean bResult = true;
			String result = "";
			if (Model.workDir.equals(null)) {
				result += "Null in direcory\n";
				bResult = false;
			}
			if (!pathCheck(workDir)) {
				result += "Directory not found\n";
				bResult = false;
			}
			if (numArg == 1)
				arg1 = workDir + File.separator + arg1;
			else {
				result += "Bad format of command\n";
				bResult = false;
			}
			happendError = !bResult;
			return result;
		}
	}

	public class Exit extends CommandInterface {
		public static final String id = "exit";
		public boolean happendError = false;

		public ArrayList<String> getResult() {
			System.exit(0);
			return null;
		}

		public String getError() {
			return "";
		}
	}

	private boolean pathCheck(String directory) {
		File f = new File(directory);
		return f.exists();
	}

	private String getAbsPath(String directory, String arg) {
		// translate name of file and work directory in absolute directory
		File fArg = new File(arg);
		String absDir = arg;
		if (!fArg.exists())
			absDir = directory + File.separator + arg;
		return absDir;
	}

}
